/*
 * Copyright © 2022-now 尛飛俠（Denvie） All rights reserved.
 */

package cn.denvie.sentinel.persistent.pull.file.assist;

import com.alibaba.cloud.sentinel.datasource.converter.SentinelConverter;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateGroupItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateItem;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.node.ObjectNode;

import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * SentinelConverterWrapper。
 * JSON序列化参考 SentinelGatewayAutoConfiguration 实现。
 *
 * @author Denvie
 * @date 2022/4/10
 * @since 1.0.0
 */
public class SentinelConverterWrapper<T> extends SentinelConverter<T> {
    private static final Map<Class<?>, SentinelConverterWrapper<?>> converterMap;
    private static final ObjectMapper objectMapper;

    static {
        converterMap = new HashMap<>();

        objectMapper = new ObjectMapper();
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,
                false);
        ApiPredicateItemDeserializer deserializer = new ApiPredicateItemDeserializer();
        deserializer.registerApiPredicateItem("pattern",
                ApiPathPredicateItem.class);
        deserializer.registerApiPredicateItem("items",
                ApiPredicateGroupItem.class);
        SimpleModule module = new SimpleModule(
                "PolymorphicApiPredicateItemDeserializerModule",
                new Version(1, 0, 0, null, null, null));
        module.addDeserializer(ApiPredicateItem.class, deserializer);
        objectMapper.registerModule(module);
    }

    public SentinelConverterWrapper(ObjectMapper objectMapper, Class<T> ruleClass) {
        super(objectMapper, ruleClass);
    }

    public static Collection<?> convert(Class<?> ruleClass, String source) {
        SentinelConverterWrapper<?> sentinelConverter = converterMap.get(ruleClass);
        if (sentinelConverter == null) {
            sentinelConverter = new SentinelConverterWrapper<>(objectMapper, ruleClass);
            converterMap.put(ruleClass, sentinelConverter);
        }
        return sentinelConverter.convert(source);
    }

    public static String writeValueAsString(Object value) {
        try {
            return objectMapper.writeValueAsString(value);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }

    private static class ApiPredicateItemDeserializer
            extends StdDeserializer<ApiPredicateItem> {

        private final Map<String, Class<? extends ApiPredicateItem>> registry = new HashMap<>();

        ApiPredicateItemDeserializer() {
            super(ApiPredicateItem.class);
        }

        void registerApiPredicateItem(String uniqueAttribute,
                                      Class<? extends ApiPredicateItem> apiPredicateItemClass) {
            registry.put(uniqueAttribute, apiPredicateItemClass);
        }

        @Override
        public ApiPredicateItem deserialize(JsonParser jp,
                                            DeserializationContext ctxt) throws IOException {
            ObjectMapper mapper = (ObjectMapper) jp.getCodec();
            ObjectNode root = mapper.readTree(jp);
            Class<? extends ApiPredicateItem> apiPredicateItemClass = null;
            Iterator<Map.Entry<String, JsonNode>> elementsIterator = root.fields();
            while (elementsIterator.hasNext()) {
                Map.Entry<String, JsonNode> element = elementsIterator.next();
                String name = element.getKey();
                if (registry.containsKey(name)) {
                    apiPredicateItemClass = registry.get(name);
                    break;
                }
            }
            if (apiPredicateItemClass == null) {
                return null;
            }
            return mapper.readValue(root.toString(), apiPredicateItemClass);
        }
    }
}
